/************************************************************************/
/*  Acorn Computers Ltd, 1992.                                         */
/*                                                                      */
/* This file forms part of an unsupported source release of RISC_OSLib. */
/*                                                                      */
/* It may be freely used to create executable images for saleable       */
/* products but cannot be sold in source form or as an object library   */
/* without the prior written consent of Acorn Computers Ltd.            */
/*                                                                      */
/* If this file is re-distributed (even if modified) it should retain   */
/* this copyright notice.                                               */
/*                                                                      */
/************************************************************************/

/* Title: c.menu
 * Purpose: menu manipulation.
 * History: IDJ: 06-Feb-92: prepared for source release
 */

#define BOOL int
#define TRUE 1
#define FALSE 0

#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "h.os"
#include "h.wimp"
#include "h.werr"
#include "h.menu"

#include "h.sprite"
#include "h.resspr"
#include "h.msgs"

typedef struct menu__str
{
  wimp_menuhdr *m;         /* the wimp-level menu that we've built. */
  int nitems;
  void *entryspace;        /* for sub-menus, and entries with >12 chars */
  int nbytes;
  int maxentrywidth;
} menu__str;
/* concrete representation of abstract menu object */

/* The menu__str structure points to a RISCOS-style menu, and to a separate
buffer of workspace for sub-menu pointers and for string fields that
are >12 characters. The format of the entryspace is:
  each sub-Menu pointer
  a Menu(NIL) word
  each entry buffer
*/



static wimp_menuitem *menu__itemptr(menu m, int n)
/* Pointer to the nth item in the menu (starting at 0). */
{
   return(((wimp_menuitem*)(m->m + 1)) + n);
}


/* -------- Building RISC OS Menus. -------- */

/* The menu is assembled entry by entry in temporary workspace, then copied
to more precisely allocated store. The copying of menu structures is split
into the allocation of store and then the copying of data, so that the copy
into the larger buffer can share the latter half of the operation. */



static void menu__disposespace(menu m)
{
   if (m->m != 0)
   {
      free(m->m);
      m->m = 0;
   }
   if (m->entryspace != 0)
   {
      /* can only happen with very new menu__strs. */
      free(m->entryspace);
      m->entryspace = 0;
   }
}


static void menu__preparecopy(menu from, menu to)
{
   /* Allocate space in the destination to suit the copy. */
   to->m = malloc(sizeof(wimp_menuhdr) + from->nitems * sizeof(wimp_menuitem));
   if (to->m == 0)
   {
      werr(TRUE, msgs_lookup("menu1:Not enough memory for menu (1) -- increase wimpslot"));
   }
   if (from->nbytes != 0)
   {
      to->entryspace = malloc(from->nbytes);
      if (to->entryspace == 0)
      {
         werr(TRUE, msgs_lookup("menu2:Not enough memory for menu (2) -- increase wimpslot"));
      }
   }
   else
   {
      to->entryspace = 0;
   }
}


static void menu__copydata(menu from, menu to)
/* Copy the data and lengths. Relocate indirection pointers. */
{
   int moveoffset;
   int i;

   to->maxentrywidth = from->maxentrywidth;
   to->nitems = from->nitems;
   (void) memmove(to->m, from->m,
     sizeof(wimp_menuhdr) + from->nitems * sizeof(wimp_menuitem));
   to->nbytes = from->nbytes;
   moveoffset = ((char*)to->entryspace) - ((char*)from->entryspace);
   (void) memmove(to->entryspace, from->entryspace, from->nbytes);
   for (i=0;i<to->nitems;i++)
   {
      wimp_menuitem *ptr = menu__itemptr(to, i);
      if ((ptr->iconflags & wimp_INDIRECT) != 0)
      {
         ptr->data.indirecttext.buffer += moveoffset;
      }
   }
}

/* The work area is allocated on the stack, with the following limits: */

#define MAXITEMS 120       /* max size of a menu */
#define MAXENTRYSPACE 2800 /* space for building entries > 12 chars */

typedef struct
{
  menu__str m;
  wimp_menuhdr menuhdr;
  wimp_menuitem menuitems[MAXITEMS];
  char entryspace[MAXENTRYSPACE];
} menu__workarea;

static void menu__initworkarea(menu__workarea *w)
{
   w->m.m = &w->menuhdr;
   w->m.nitems = 0;
   w->m.entryspace = &w->entryspace;
   w->m.maxentrywidth = 0;
   /* insert a NIL in the entrySpace to distinguish sub-Menu pointers
   from text space. */
   w->m.nbytes = 4;
   *((int*)w->entryspace) = 0;
}


static void menu__copytoworkarea(menu m /*in*/, menu__workarea *w /*out*/)
{
   menu__initworkarea(w);
   menu__copydata(m, &w->m);
   menu__itemptr(&w->m, w->m.nitems-1)->flags &= ~wimp_MLAST;
}


static void menu__copyworkarea(menu__workarea *w /*in*/, menu m /*out*/)
{
   if (w->m.nitems > 0)
   {
      menu__itemptr(&w->m, w->m.nitems-1)->flags |= wimp_MLAST;
   }
   menu__disposespace(m);
   menu__preparecopy(&w->m, m);
   menu__copydata(&w->m, m);
}

/* -------- Creating menu descriptions. -------- */

static void menu__initmenu(char *name, menu m /*out*/)
{
   int i;
   for (i=0; i<12; i++)
   {
      m->m->title[i] = name[i];
      if (name[i]==0) {break;}
   }
   m->m->tit_fcol = 7; /* title fore: black */
   m->m->tit_bcol = 2;  /* title back: grey */
   m->m->work_fcol = 7; /* entries fore */
   m->m->work_bcol = 0; /* entries back */
   m->m->width = i*16;  /* minimum value */
   m->m->height = 44;   /* per entry */
   m->m->gap = 0;       /* gap between entries, in OS units */
}

/* >>>> titles of more than 12 characters impossible */

static int menu__max(int a, int b)
  { if (a < b) {return(b);} else {return(a);} }

static wimp_menuitem *menu__additem(menu__workarea *w /*out*/, char *name, int length)
/* The returned pointer can be used to set flags, etc. */
{
   wimp_menuitem *ptr;
int  overflow=0;

   if (w->m.nitems == MAXITEMS)
   {
      return(menu__itemptr(&w->m, MAXITEMS-1));
   }
   ptr = menu__itemptr(&w->m, w->m.nitems++);
   ptr->flags = 0;
   ptr->submenu = (wimp_menustr*) -1;
   ptr->iconflags = wimp_ITEXT + wimp_IFILLED + wimp_IVCENTRE + (7*wimp_IFORECOL);

   if (length > w->m.maxentrywidth)
   {
      w->m.maxentrywidth = length;
      w->m.m->width = menu__max(w->m.m->width, 16 + length * 16);
       /* in OS units, 16 per char. */
   }

if((length > 12) && (length+1+w->m.nbytes >= MAXENTRYSPACE))
{
   length = 12;
   overflow=1;
}

   if (length <= 12)
   {
      int i;
      for (i=0; i<length; i++) {ptr->data.text[i] = name[i];}
      if (length < 12) {ptr->data.text[length] = 0;}
      if(overflow) {ptr->data.text[11] = '*';}
   }
   else
   {
      ptr->iconflags += wimp_INDIRECT;
      ptr->data.indirecttext.buffer = ((char*)w->m.entryspace) + w->m.nbytes;
      ptr->data.indirecttext.validstring = (char*) -1;
      ptr->data.indirecttext.bufflen = 100;
      (void) memmove(((char*)w->m.entryspace) + w->m.nbytes, name, length);
      w->m.nbytes += length + 1;
      ((char*)w->m.entryspace)[w->m.nbytes-1] = 0; /* terminate the string. */
   }

   return(ptr);
}

/* -------- Parsing Description Strings. -------- */

static void menu__syntax(void)
{
  /* do nothing */
}

typedef enum
{
  TICK = 1,
  FADED = 2,
  DBOX = 4
} opt;

typedef enum {OPT, SEP, NAME, END} toktype;

typedef struct
{
  char *s;
  toktype t;
  char ch;        /* last separator char encountered */
  opt opts;       /* last opts encountered */
  char *start;
  char *end;      /* last name encountered */
} parser;

static void menu__initparser(parser *p, char *s)
{
   p->s = s;
   p->ch = ',';
}

static void menu__getopt(parser *p)
{
   p->opts = 0;
   while (p->ch=='!' || p->ch=='~' || p->ch=='>' || p->ch==' ')
   {
      if (p->ch=='!')
      {
         p->opts |= TICK;
      }
      else if (p->ch=='~')
      {
         p->opts |= FADED;
      }
      else if (p->ch=='>')
      {
         p->opts |= DBOX;
      }
      p->ch=*p->s++;
   }
   p->s--;
}

static void menu__getname (parser *p)

{
   /*Skip leading spaces*/
   while (p->ch == ' ')
     p->ch = *p->s++;

   p->start = p->s - 1;

   if (p->ch == '"')
   {
      /*Quoted string*/
      p->ch = *p->s++;

      p->start = p->s - 1;

      while (p->ch != 0 && p->ch != '"')
        p->ch = *p->s++;

      p->end = p->s - 1;

      if (p->ch == '"')
      {
         p->ch = *p->s++;

         /*Skip trailing spaces*/
         while (p->ch == ' ')
               p->ch = *p->s++;
      }

      if (p->ch != 0 && p->ch != ',' && p->ch != '|')
        p->ch = *p->s++;

      p->s--;
   }
   else
   {
      /*Non-quoted string*/
      p->start = p->s - 1;

      while (p->ch != 0 && p->ch != ',' && p->ch != '|')
        p->ch = *p->s++;

      p->end = --p->s;
   }
}

static toktype menu__gettoken(parser *p)
{
   p->ch = ' ';
   while (p->ch == ' ') p->ch = *p->s++;

   switch (p->ch)
   {
      case 0:
          p->t = END;
          break;
      case '!':
      case '~':
      case '>':
          p->t = OPT;
          menu__getopt(p);
          break;
      case ',':
      case '|':
          p->t = SEP;
          break;
      default:
          p->t = NAME;
          menu__getname(p);
          break;
   }

   return(p->t);
}



/* -------- Parsing and Extension. -------- */

static void menu__doextend(menu__workarea *w, char *descr)
{
   parser p;
   toktype tok;
   wimp_menuitem *ptr;

   menu__initparser(&p, descr);
   tok = menu__gettoken(&p);
   if (tok==END)
   {
      /* do nothing */
   }
   else
   {
      if (tok==SEP)
      {
         if (w->m.nitems == 0)
         {
            menu__syntax();
         }
         else
         {
            if (p.ch == '|')
            {
               ptr = menu__itemptr(&w->m, w->m.nitems-1);
               ptr->flags |= wimp_MSEPARATE;
             }
             tok = menu__gettoken(&p);
         }
      }

      while (1)
      {
         if (tok == OPT)
         {
            tok = menu__gettoken(&p); /* must be NAME, check below */
         }
         else
         {
            p.opts = 0;
         }
         if (p.t != NAME)
         {
            menu__syntax();
         }
         else
         {
            ptr = menu__additem(w, p.start, p.end - p.start);
            if ((TICK & p.opts) != 0)
            {
               ptr->flags |= wimp_MTICK;
            }
            if ((FADED & p.opts) != 0)
            {
              ptr->iconflags |= wimp_INOSELECT;
            }
            if ((DBOX & p.opts) != 0)
            {
               ptr->flags |= wimp_MSUBLINKMSG;
               ptr->submenu = (wimp_menustr*) 1;
            }
            tok = menu__gettoken(&p);
            if (tok == END) break;
            if (tok != SEP)
            {
               menu__syntax();
            }
            else
            {
               if (p.ch == '|') ptr->flags |= wimp_MSEPARATE;
            }
         }
         tok = menu__gettoken(&p);
      }
   }
}

/* -------- Entrypoints. -------- */


menu menu_new(char *name, char *descr)
{
   menu m;
   menu__workarea menu__w;
   menu__initworkarea(&menu__w);
   menu__initmenu(name, &(menu__w.m));
   menu__doextend(&menu__w, descr);
   m = malloc(sizeof(menu__str));
   if (m == 0)
   {
      werr(TRUE, msgs_lookup("menu3:Not enough memory for menu (3) -- increase wimpslot"));
   }
   m->m = 0;
   m->entryspace = 0;
   menu__copyworkarea(&menu__w, m);

   return m;
}


void menu_dispose(menu *m, int recursive)
{
   if (recursive != 0)
   {
      menu *a = (menu*) ((*m)->entryspace);
      while (1)
      {
         menu subm = *(a++);
         if (subm == 0) {break;}
         menu_dispose(&subm, 1);
      }
   }
   menu__disposespace(*m);
   free(*m);
}


void menu_extend(menu m, char *descr)
{
   menu__workarea menu__w;
   menu__copytoworkarea(m, &menu__w);
   menu__doextend(&menu__w, descr);
   menu__copyworkarea(&menu__w, m);
}


void menu_setcolour(menu m, int entry, int wimpcolr)
/*********************************************/
{
   wimp_menuitem *p;

   if (entry == 0) {return;}
   if (entry > m->nitems) {return;}

   p = menu__itemptr(m, entry-1);

   p->iconflags = (p->iconflags & 0xf0ffffff) | (wimpcolr << 24);
}   /* end of menu_setcolour */



void menu_setflags(menu m, int entry, int tick, int fade)
{
   wimp_menuitem *p;

   if (entry == 0) {return;}
   if (entry > m->nitems) {return;}

   p = menu__itemptr(m, entry-1);

   if (tick != 0)
   {
      p->flags |= wimp_MTICK;
   }
   else
   {
      p->flags &= ~wimp_MTICK;
   }

   if (fade != 0)
   {
      p->iconflags |= wimp_INOSELECT;
   }
   else
   {
      p->iconflags &= ~wimp_INOSELECT;
   }
}

void menu_make_writeable(menu m, int entry, char *buffer, int bufferlength,
                         char *validstring)
{
   wimp_menuitem *p;
   if (entry == 0) {return;}
   if (entry > m->nitems) {return;}
   p = menu__itemptr(m, entry-1);
   p->flags |= wimp_MWRITABLE ;
   p->iconflags |= wimp_BWRITABLE * wimp_IBTYPE + wimp_INDIRECT +
                   wimp_IHCENTRE + wimp_IVCENTRE + wimp_ITEXT ;
   p->data.indirecttext.buffer = buffer ;
   p->data.indirecttext.bufflen = bufferlength ;
   p->data.indirecttext.validstring = validstring ;
}


void menu_make_sprite(menu m, int entry, char *spritename)
{
   wimp_menuitem *p;

   if (entry == 0) {return;}
   if (entry > m->nitems) {return;}
   p = menu__itemptr(m, entry-1);


   p->iconflags &= ~wimp_ITEXT;
   p->iconflags |= wimp_INDIRECT+wimp_IVCENTRE+wimp_ISPRITE;
   p->data.indirectsprite.name = spritename;
   p->data.indirectsprite.spritearea = resspr_area();
   p->data.indirectsprite.nameisname = 1;
}


void menu_submenu(menu m, int place, menu submenu)
{
   int i;
   wimp_menuitem *p = menu__itemptr(m, place-1);
   menu__workarea menu__w;

   p->submenu = (wimp_menustr*) submenu->m;
   menu__copytoworkarea(m, &menu__w);
   (void) memmove(((menu*) menu__w.m.entryspace) + 1, ((menu*) menu__w.m.entryspace), menu__w.m.nbytes);
   menu__w.m.nbytes += sizeof(menu*);
   *((menu__str**)(menu__w.m.entryspace)) = submenu;

   for (i=0; i<menu__w.m.nitems; i++)
   {
      p = menu__itemptr(&menu__w.m, i);
      if (((p->iconflags)&wimp_INDIRECT) != 0)
      {
          p->data.indirecttext.buffer += 4;
      }
   }
   menu__copyworkarea(&menu__w, m);
}

void *menu_syshandle(menu m)
{
   return (void *) m->m;
}

/* end */
